#include "linux/tty.h"
#include "asm/io.h"

#define CRT_ADDR_REG 0x3d4 // CRT(6845)索引寄存器
#define CRT_DATA_REG 0x3d5 // CRT(6845)数据寄存器

#define CRT_START_ADDR_H 0xc // 显示内存起始位置，高位
#define CRT_START_ADDR_L 0xd // 显示内存起始位置，低位
#define CRT_CURSOR_H 0xe    // 光标位置，高位
#define CRT_CURSOR_L 0xf    // 光标位置，低位

#define WIDTH 80                      // 屏幕文本列数
#define HEIGHT 25                     // 屏幕文本行数

#define MEM_BASE 0xb8000
#define MEM_SIZE 0x4000
#define MEM_END (MEM_BASE + MEM_SIZE)

#define ROW_SIZE (WIDTH * 2)          // 每行字节数
#define SCR_SIZE (ROW_SIZE * HEIGHT)  // 屏幕字节数

#define ASCII_NUL 0x00
#define ASCII_ENQ 0x05
#define ASCII_BEL 0x07 // \a
#define ASCII_BS 0x08  // \b
#define ASCII_HT 0x09  // \t
#define ASCII_LF 0x0a  // \n回车
#define ASCII_VT 0x0b  // \v
#define ASCII_FF 0x0c  // \f
#define ASCII_CR 0x0d  // \r换行
#define ASCII_DEL 0x7f

static u32 screen;
static u32 x, y;

static void set_cursor()
{
    int pos = (y * WIDTH + x) * 2;
    out_byte(CRT_ADDR_REG, CRT_CURSOR_H);
    out_byte(CRT_DATA_REG, (pos >> 9) & 0xff);
    out_byte(CRT_ADDR_REG, CRT_CURSOR_L);
    out_byte(CRT_DATA_REG, (pos >> 1) & 0xff);
}

static void set_screen()
{
    out_byte(CRT_ADDR_REG, CRT_START_ADDR_H);
    out_byte(CRT_DATA_REG, (screen >> 9) & 0xff);
    out_byte(CRT_ADDR_REG, CRT_START_ADDR_L);
    out_byte(CRT_DATA_REG, (screen >> 1) & 0xff);
}

void console_clear()
{
    screen = 0;
    x = 0;
    y = 0;
    set_cursor();
    set_screen();

    u16* ptr = (u16*) MEM_BASE;
    while (ptr < (u16*) MEM_END)
    {
        *ptr++ = 0x0720;
    }
}

void console_init()
{
    console_clear();
}

static void command_bs()
{
    if (x > 0)
    {
        x--;
    }
    else if (y > 0)
    {
        x = WIDTH - 1;
        y--;
    }
    *(u16 *)(MEM_BASE + (y * ROW_SIZE + x) * 2) = 0x0720;
}

// 向上滚屏
static void scroll_up()
{
    if (screen + SCR_SIZE + ROW_SIZE < MEM_END)
    {
        u32 *ptr = (u32 *)(screen + SCR_SIZE);
        for (size_t i = 0; i < WIDTH; i++)
        {
            *ptr++ = 0x0720;
        }
        screen += ROW_SIZE;
        y++;
    }
    else
    {
        memcpy(MEM_BASE, screen, SCR_SIZE);
        y--;
        screen = MEM_BASE;
    }
    set_screen();
}

static void command_lf() 
{
    if (y + 1 < HEIGHT)
    {
        y++;
    }
    else
    {
        scroll_up();
    }

}

static void command_cr()
{
    x = 0;
}

static void command_del()
{
    *(u16 *)(MEM_BASE + (y * ROW_SIZE + x) * 2) = 0x0720;
}

void console_write(char *buf, u32 count)
{
    u8 *ptr = (u8 *)(MEM_BASE + (y * ROW_SIZE + x) * 2);
    while (count--)
    {
        char ch = *buf++;
        switch (ch)
        {
        case ASCII_NUL:
            break;
        case ASCII_BEL:
            break;
        case ASCII_BS:
            command_bs();
            break;
        case ASCII_HT:
            break;
        case ASCII_LF:
            command_lf();
            command_cr();
            break;
        case ASCII_VT:
            break;
        case ASCII_FF:
            command_lf();
            break;
        case ASCII_CR:
            command_cr();
            break;
        case ASCII_DEL:
            command_del();
            break;
        default:
            if (x >= WIDTH) 
            {
                x = 0;
                command_lf();
            }
            *ptr++ = ch;
            *ptr++ = 0x07;
            x++;
            break;
        }
        
    }
    set_cursor();
}